package chapter_09

import java.io.{File, PrintWriter}


object chapter_09 {

  private def filesHere = (new File("./src./main/scala/chapter_19")).listFiles

  def filesMatchingFirst(query: String, matcher: (String, String) => Boolean) = {
    for (file <- filesHere; if matcher(file.getName, query))
      println(file)
  }

  def filesEndingFirst(query: String) =
    filesMatchingFirst(query, (fileName, query) => fileName.endsWith(query))
//    filesMatchingFirst(query, _.endsWith(_))

  def filesContainingFirst(query: String) =
    filesMatchingFirst(query, _.endsWith(_))

  def filesRegexFirst(query: String) =
    filesMatchingFirst(query, _.startsWith(_))

  /*
  在filesEndingFirst方法里的函数字面量_.endsWith(_)，与下面是一回事：
  (fileName: String, query: String) => fileName.endsWith(query)
  原因是filesMatching带一个函数，这个函数需要两个String参数，不过无须指定参数类型。因
  此，你也可以写成(fileName, query) => fileName.endsWith(query)。由于第一个参数
  fileName在方法体中被第一个使用，第二个参数query被第二个使用，你也可以使用占位符语法：
  _.endsWith(_)。第一个下划线是第一个参数文件名的占位符，第二个下划线是第二个参数查询字符
  串的占位符。
  _.endsWith(_)和_.contains(_)，都是在运行期实例化成函数值而不是闭包，因为它们没有捕
  获任何自由变量。举例来说，表达式_.endsWith(_)里用的两个变量，都是用下划线代表的，也就是
  说它们都是从传递给函数的参数获得。因此，_endsWith(_)使用了两个绑定变量，而不是自由变量。
  相对地，最近的例子里面用到的函数字面量_.endsWith(query)包含一个绑定变量，下划线代表
  的参数，和一个名为query的自由变量。仅仅是因为scala支持闭包才使得你可以在最近的这个例子里从
  filesMatching中去掉query参数，从而进一步简化了代码。
   */
  def filesMatching(matcher: String => Boolean): Unit = {
    for (
      file <- filesHere
      if matcher(file.getName)
    )
      println(file)
  }

  def filesEnding(query: String) =
    filesMatching(_.endsWith(query))

  def filesContaining(query: String) =
    filesMatching(_.contains(query))

  def filesRegex(query: String) =
    filesMatching(_.matches(query))

  def existsTest(): Unit = {
    // exists
    val listSet: List[Int] = List(1, 2, 3, 4)
    println(listSet.exists(_ < 0))
    println(listSet.exists(_ % 2 == 0))
  }

  def curryingTest(): Unit = {
    // 柯里化
    def plainOldSum(x: Int, y: Int) = x + y
    println(plainOldSum(1, 2))

    def curriedSum(x: Int)(y: Int) = x + y
    println(curriedSum(1)(2))

    // 获得实际指向curriedSum的“第二个”函数的参考
    val onePlus = curriedSum(1)_  // 下划线前面的空格是可选的
    println(onePlus(2))
  }

  def createANewFunc {
    // 编写一个控制结构
    def twice(op: Double => Double, x: Double) = op(op(x))
    println(twice(_ + 1.0, 5))
  }

  def withPrintWriter(file: File, op: PrintWriter => Unit): Unit = {
    // 打开一个资源，对它进行操作，然后关闭资源
    val writer = new PrintWriter(file)
    try {
      op(writer)
    } finally {
      writer.close()
    }
  }

  def replaceCall(): Unit = {
    // 一个参数的调用可以使用花括号替代圆括号
    println{"Hello, World!"}
  }

  def withPrintWriter2(file: File)(op: PrintWriter => Unit): Unit = {
    // 柯里化withPrintWriter，使得其能使用花括号
    val writer = new PrintWriter(file)
    try {
      op(writer)
    } finally {
      writer.close()
    }
  }

  // 实现一个myAssert的断言架构
  def scalaAssert(): Unit = {
    var assertionsEnabled = true

    def myAssert(predicate: () => Boolean) =
      if (assertionsEnabled && !predicate())
        throw new AssertionError

    // 难看的使用
    myAssert(() => 5 > 3)
    /*
    你或许很想省略函数字面量里的空参数列表和=>符号，写成如下形式：
    myAssert(5 > 3) // 不会有效，因为缺少() =>
    传名函数恰好为了实现你的愿望而出现。要实现一个传名函数，要定义参数的类型开始于=>而不是
    ()=>。例如，你可以通过改变其类型，“()=>Boolean”，为“=>Boolean”，把myAssert的
    predicate参数改为传名参数，如下：
     */
    def byNameAssert(predicate: => Boolean) =
      if (assertionsEnabled && !predicate)
        throw new AssertionError
    byNameAssert(5 > 3)

    def boolAssert(predicate: Boolean) =
      // 普通方式
      if (assertionsEnabled && !predicate)
        throw new AssertionError
    /*
    虽然如此，这两种方式之间存在一个非常重要的差别须指出。因为byNameAssert的参数类型是
    Boolean，在byNameAssert(5 > 3)里括号的表达式先于byNameAssert的调用被评估。表达式5>3
    产生true，被传给byNameAssert。相对的，因为boolAssert的predicate参数类型是=>
    Boolean，boolAssert(5 > 3)里括号中的表达式不是先于boolAssert的调用被评估的。而是
    代之以先创建一个函数值，其apply方法将评估5 > 3，而这个函数值将被传递给myAssert。
    因此，这两种方式之间的差别，在于如果断言被禁用，你会看到boolAssert括号里的表达式的某
    些副作用，而byNameAssert却没有。例如，如果断言被禁用，boolAssert的例子里尝试对“x/0
    == 0”的断言将产生一个异常：
     */
    assertionsEnabled = false
    byNameAssert(1 / 0 == 0)
    boolAssert(1 / 0 == 0)
  }

  def main(args: Array[String]): Unit = {
    filesEnding(".scala")
//    existsTest
//    curryingTest
//    createANewFunc

//    withPrintWriter(
//      new File("README.txt"),
//      writer => writer.println(new java.util.Date)
//    )

//    replaceCall

//    withPrintWriter2(new File("README.txt")) {
//      writer => writer.println(new java.util.Date)
//    }

//    scalaAssert
  }
}
